using System;
using System.Collections.Generic;
using System.Threading;
using System.Linq;
using System.Web;
using System.Web.Caching;
using PluginScheduler.Interfaces;
using PluginScheduler.Jobs;

namespace PluginScheduler
{

    public class BaseScheduler : IScheduler
    {
        private const string DummyCacheItemKey = "DummyCacheItemKey";
        private readonly HttpContext _currentContext;

        private readonly Dictionary<JobKey, IJobDetail> _jobs = new Dictionary<JobKey, IJobDetail>();
        private readonly List<JobKey> _pausedJobs = new List<JobKey>();
        private TimeSpan _startInterval;
        private volatile bool closed;
        private volatile bool shuttingDown;
        private DateTimeOffset? initialStart;
        private int _numJobsExecuted;

        public string CurrentlyExecutingJobName { get; private set; }

        public BaseScheduler(HttpContext currentContext)
        {
            _currentContext = currentContext;
        }

        public virtual bool IsShutdown
        {
            get { return closed; }
        }

        public virtual bool IsShuttingDown
        {
            get { return shuttingDown; }
        }

        public virtual bool IsStarted
        {
            get { return !shuttingDown && !closed && initialStart != null; }
        }

        public virtual void Start(TimeSpan startInterval)
        {
            if (shuttingDown || closed)
            {
                throw new Exception("The Scheduler cannot be restarted after Shutdown() has been called.");
            }

            if (!initialStart.HasValue)
            {
                initialStart = DateTime.Now;
            }

            _pausedJobs.Clear();
            _numJobsExecuted = 0;

            _startInterval = startInterval;
            RegisterCacheEntry();
        }

        public virtual void Standby()
        {

        }

        public void Shutdown()
        {
            this.Shutdown(false);
        }

        public virtual DateTimeOffset? RunningSince
        {
            get { return initialStart; }
        }

        public virtual int NumJobsExecuted
        {
            get { return _numJobsExecuted; }
        }

        public virtual void Shutdown(bool waitForJobsToComplete)
        {
            try
            {
                if (shuttingDown || closed)
                {
                    return;
                }

                shuttingDown = true;

                Standby();

                if (waitForJobsToComplete)
                {
                    while (_numJobsExecuted < _jobs.Count)
                    {
                        try
                        {
                            Thread.Sleep(100);
                        }
                        catch (ThreadInterruptedException)
                        {
                        }
                    }
                }

            }
            catch(Exception ex)
            {
                //TODO::ADD log
            }
            closed = true;
        }

        public virtual void ValidateState()
        {
            if (IsShutdown)
            {
                throw new Exception("The Scheduler has been Shutdown.");
            }

            // other conditions to check (?)
        }

        public virtual void AddJob(IJobDetail jobDetail)
        {
            ValidateState();

            if (jobDetail == null)
            {
                throw new Exception("JobDetail cannot be null");
            }

            if (jobDetail.Key == null)
            {
                throw new Exception("Job's key cannot be null");
            }

            if (jobDetail.JobType == null)
            {
                throw new Exception("Job's class cannot be null");
            }

            AddJob(jobDetail, false);
        }

        public virtual void AddJob(IJobDetail jobDetail, bool replace)
        {
            ValidateState();

            if (replace)
            {
                if (_jobs.ContainsKey(jobDetail.Key))
                    _jobs.Remove(jobDetail.Key);
            }
            else
            {
                if (_jobs.ContainsKey(jobDetail.Key))
                    throw new Exception("Cant add job. job with the same key already exists");
            }

            _jobs.Add(jobDetail.Key, jobDetail);
        }

        public virtual bool DeleteJob(JobKey jobKey)
        {
            ValidateState();

            var deleted = false;

            if (_jobs.ContainsKey(jobKey))
                deleted = _jobs.Remove(jobKey);

            if (_pausedJobs.Contains(jobKey))
                _pausedJobs.Remove(jobKey);

            return deleted;
        }

        public bool DeleteJobs(IList<JobKey> jobKeys)
        {
            ValidateState();
            var state = true;
            foreach (var jobKey in jobKeys)
            {
                if (!this.DeleteJob(jobKey))
                    state = false;
            }
            return state;
        }

        public virtual void PauseJob(JobKey jobKey)
        {
            ValidateState();

            if (!this._pausedJobs.Contains(jobKey))
                this._pausedJobs.Add(jobKey);
        }

        public virtual void ResumeJob(JobKey jobKey)
        {
            ValidateState();

            if (this._pausedJobs.Contains(jobKey))
                this._pausedJobs.Remove(jobKey);
        }

        public virtual void PauseAll()
        {
            ValidateState();

            foreach (var jobDetail in _jobs)
            {
                PauseJob(jobDetail.Key);
            }
        }

        public virtual void ResumeAll()
        {
            ValidateState();
            _pausedJobs.Clear();
        }

        public bool CheckExists(JobKey jobKey)
        {
            ValidateState();

            return _jobs.Any(x => x.Key == jobKey);
        }

        public void Clear()
        {
            ValidateState();

            this._pausedJobs.Clear();
            this._jobs.Clear();
        }

        private void RegisterCacheEntry()
        {
            try
            {
                if (null != _currentContext.Cache[DummyCacheItemKey]) return;

                _currentContext.Cache.Add(
                    DummyCacheItemKey,
                    "Test",
                    null,
                    DateTime.MaxValue,
                    _startInterval,
                    CacheItemPriority.Normal,
                    CacheItemRemovedCallback);
            }
            catch (Exception ex)
            {
                //_log.Info(string.Format("Exception in RegisterCacheEntry: {0} - {1}", DateTime.Now, ex.Message));
            }
        }

        private void CacheItemRemovedCallback(string key, object value, CacheItemRemovedReason reason)
        {
            //_log.Info(string.Format("Enter into CacheItemRemovedCallback: {0}", DateTime.Now));

            try
            {
                foreach (var jobDetail in _jobs.Values)
                {
                    var job = CreateJobInstance(jobDetail);
                    if (job == null)
                    {
                        //TODO:: write to log
                        continue;
                    }

                    CurrentlyExecutingJobName = jobDetail.JobType.FullName;
                    try
                    {
                        job.Execute(jobDetail);
                    }
                    catch (Exception ex)
                    {
                        //TODO::Write to log
                    }
                    ++_numJobsExecuted;
                }


                RegisterCacheEntry();
            }
            catch (Exception ex)
            {
                //_log.Info(string.Format("Exception in RunWork: {0} - {1}", ex.Message, ex.StackTrace));
            }
        }

        private IJob CreateJobInstance(IJobDetail jobDetail)
        {
            var jobType = jobDetail.JobType;
            var job = this.InstantiateType(jobType);
            return job;
        }

        private IJob InstantiateType(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type", "Cannot instantiate null");
            }
            var ci = type.GetConstructor(Type.EmptyTypes);
            if (ci == null)
            {
                throw new ArgumentException("Cannot instantiate type which has no empty constructor", type.Name);
            }
            return (IJob)ci.Invoke(new object[0]);
        }
    }

    /// <summary>
    /// ErrorLogger - Scheduler Listener Class
    /// </summary>
    internal class ErrorLogger
    {
        public void SchedulerError(string msg)
        {
            //Log.Error(msg);
        }
    }
}